﻿// MinMathParser to demonstrate very simplyfied expression parser
// -------------------------------------------------------------------
// License: MS-RL License
// Version: 1.0.0.0
// Initial Development: Prosodev - Software Company / September 2013 / V 1.0.0.0

namespace MinMathParser
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    ///     http://en.wikipedia.org/wiki/Parsing_expression_grammar => Parsing mathematical expression with c# very short
    ///     Value   ← [0-9.]+ | '(' Expr ')'
    ///     Product ← Expr (('*' | '/') Expr)*
    ///     Sum     ← Expr (('+' | '-') Expr)*
    ///     Expr    ← Product | Sum | Value
    ///     was modified to
    ///     Value   ← [0-9.]+ | '(' Expr ')' | '-' Expr
    ///     Product ← Expr (('*' | '/') Expr)*
    ///     Sum     ← Expr (('+' | '-') Expr)*
    ///     Expr    ← Product | Sum | Value
    /// </summary>
    public static class Grammar
    {
        #region Public Methods and Operators

        public static void Eval(string argument)
        {
            var tokens = argument.TokenizeByNonDigits();
            var index = 0;
            try
            {
                var res = Expr(tokens, ref index);
                Console.WriteLine(argument.PadRight(45) + "=" + res);
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
            }
        }

        public static int Expr(List<string> tokens, ref int index)
        {
            if (index >= tokens.Count)
            {
                throw new Exception("Error");
            }

            return Sum(tokens, ref index);
        }

        public static int Product(List<string> tokens, ref int index)
        {
            var result = Value(tokens, ref index);
            while (index < tokens.Count && tokens[index].IsMulOrDiv())
            {
                index += 1;
                switch (tokens[index - 1])
                {
                    case "*":
                        result = result*Product(tokens, ref index);
                        break;
                    case "/":
                        result = result/Product(tokens, ref index);
                        break;
                }
            }

            return result;
        }

        public static int Sum(List<string> tokens, ref int index)
        {
            var result = Product(tokens, ref index);
            while (index < tokens.Count && tokens[index].IsAddOrSub())
            {
                index += 1;
                switch (tokens[index - 1])
                {
                    case "+":
                        result = result + Product(tokens, ref index);
                        break;
                    case "-":
                        result = result - Product(tokens, ref index);
                        break;
                }
            }

            return result;
        }

        public static int Value(List<string> tokens, ref int index)
        {
            if (tokens[index].IsNum())
            {
                index += 1;
                return int.Parse(tokens[index - 1]);
            }

            if (tokens[index].IsOpenBracket())
            {
                index += 1;
                var r = Expr(tokens, ref index);
                if (tokens.Count > index && tokens[index].IsCloseBracket())
                {
                    index += 1;
                    return r;
                }
            }

            if (tokens[index] == "-")
            {
                index += 1;
                return -Expr(tokens, ref index);
            }


            throw new Exception(tokens.Aggregate(string.Empty, (current, l) => current + l).PadRight(45) +
                                "Unexpected character" + Environment.NewLine +
                                (string.Empty.PadLeft(tokens.Take(index).Sum(a => a.Length), '-') + "^").PadRight(45));
        }

        #endregion
    }
}